// 待大改 此文件需要改的地方有很多, 边使用边改

/*
1.
 如果 stop，无法二次启用，考虑 queue 里面的改成 存 ()=> new Step(),这样可以重新构建一个新的 queue，而不是一个新的 StepFlow
 add 时接收一个Step对象或一个返回Step对象的函数, 如果是一个Step对象将其包装成函数再放入queue中。
 */

/* 2
  每个step 有自己的abortController,abortSignal,abortRun方法,如果 StepFlow.stop() 取消所有step,则调用 abortController.abort()
  */

/* 3
下面的next 和 prev的 .finished 待修改
*/

/**
 * step 是这样一个对象
 */

/*
       const step = {
         finished: false,
         run: () => new Promise(), // run 用 promise 函数,考虑向下一个promise传递参数 ,promise函数体内是同步代码
         cancel: () => {},
         finish: () => {},
       }
         */

/**
 * StepFlow 接受上面的 step 对象
 * 没测试 remove， 动态增删可能会有问题,实际应用中这样做比较少， 动态增减step 可参考 tween.js的实现
 * chrome 在下面例子最后一个step动画会卡住,firfox不存在这个问题
 */

export class StepFlow {
  current = -1;
  currentRun = Promise.resolve(); // next 用
  currentStep;
  queue = [];
  activatedStep = [];
  add(step) {
    this.queue.push(step);
    return this;
  }
  remove(step) {
    this.queue = this.queue.filter((elem) => elem !== step);
    return this;
  }
  has(step) {
    return this.queue.includes(step);
  }

  run(index, lastParams) {
    this.current = index;
    return (this.currentRun = this.queue[this.current].run(lastParams));
  }

  cancel(index) {
    this.queue[index].cancel();
  }

  // TODO 加以判断    start 是播放整个流程，和next,previous不能同时操作
  start(index = 0, thenCallback, catchCallback) {
    this.destroySteps();
    return this.queue
      .slice(index)
      .reduce((lastRun, step, index) => {
        return lastRun.then((lastParams) => {
          this.currentStep = step();
          this.activatedStep.push(this.currentStep);
          return this.currentStep.run(lastParams + index);
        });
      }, Promise.resolve())
      .then((res) => {
        thenCallback?.(res);
        console.log("all steps finished", res);
      })
      .catch((err) => {
        console.warn("destroySteps: ", err);
        catchCallback?.(err);
        this.destroySteps();
      });
  }
  destroySteps() {
    this.activatedStep.forEach((step) => {
      step.destroy();
    });
    this.activatedStep.length = 0;
  }

  // 结束并取消所有
  stop() {
    this.currentStep.cancel();
  }

  // pause,resume 在每个step 里面实现
  // 此功能未实现step对象示例测试
  pause() {
    this.queue[this.current].pause();
  }
  resume() {
    this.queue[this.current].resume();
  }
  // todo 需要重新实现
  previous() {
    if (this.current < 0) {
      return;
    }

    if (this.current === this.queue.length) {
      this.current--;
    }
    this.cancel(this.current);
    this.current--;
  }
  // todo 需要重新实现
  next() {
    if (this.current === this.queue.length) {
      return;
    }
    if (this.current < 0) {
      return (this.currentRun = this.run(0));
    }

    if (!this.queue[this.current].finished) {
      this.queue[this.current].finish();
    }
    this.current++;
    if (this.current < this.queue.length) {
      this.currentRun.then((lastParams) => {
        this.currentRun = this.run(this.current, lastParams);
      });
    }
  }
}

export class Step {
  finished = false;
  abortController;
  abortSignal;

  constructor() {
    this.abortController = new AbortController();
    this.abortSignal = this.abortController.signal;
  }
  // run 返回 promise ,考虑向下一个promise传递参数 ,promise函数体内是同步代码
  async run(lastParams) {}
  cancel(obj) {
    this.abortController.abort(obj);
  }
  destroy() {
    throw new Error(
      `${this.constructor.name}'s destroy method not implemented`,
    );
  }
  finish() {}

  // pause,resume 在每个step 里面实现
  // 此功能未实现step对象示例测试
  pause() {}
  resume() {}
  async abortRun(callback) {
    return new Promise((resolve, reject) => {
      if (this.abortSignal.aborted) {
        reject(this.abortSignal.reason);
        return this.destroy();
      }
      this.abortSignal.addEventListener("abort", () => {
        this.destroy();
        reject(this.abortSignal.reason);
      });
      callback(resolve);
    });
  }
  async sleep(ms) {
    return new Promise((resolve) => {
      setTimeout(() => {
        resolve();
      }, ms);
    });
  }
}

export class DelayStep extends Step {
  constructor(time) {
    super();
    this.time = time;
  }
  destroy() {}
  run(lastParams) {
    return this.abortRun(async (resolve) => {
      await this.sleep(this.time);
      resolve(lastParams);
    });
  }
}

export class DomStep extends Step {
  elem;
  ani;
  finished = false;

  createElement(text) {
    this.elem = document.createElement("section");
    this.elem.innerText = text;
    this.ani = new Animation(
      new KeyframeEffect(
        this.elem,
        [
          {
            transform: "translateX(-100%)",
          },
          {
            transform: "translateX(100%)",
          },
        ],
        {
          fill: "forwards",
          duration: 1000,
        },
      ),
    );
  }
  async run(lastParams) {
    console.error("lastParams: ", lastParams);
    return new Promise((resolve, reject) => {
      this.finished = false;
      document.body.appendChild(this.elem);
      this.ani.play();
      this.ani.finished
        .then((ani) => {
          ani.commitStyles();
          this.finished = true;
          resolve(this.elem.innerText);
        })
        .catch((err) => {
          reject(err);
          console.warn("err: ", err);
        });
    });
  }
  finish() {
    this.ani.finish();
    this.finished = true;
  }
  cancel() {
    this.ani.cancel();
    document.body.removeChild(this.elem);
  }
}
